fix: validate batch translations and heal keys missing from target locales - #9
Merged
Merged
Conversation
…cales
Two failure modes let `translate` report success while writing empty locale
files and committing a fully-populated lock (poisoned state):
1. translateBatch used generateObject with a Record schema, which compiles to
additionalProperties-only JSON schema. Gemini (via OpenRouter) silently
returns {} for such schemas and OpenAI strict mode rejects them outright.
Now uses generateText with robust JSON extraction and strict validation:
every requested key must come back as a non-empty string, with one
corrective retry for missing keys before the batch hard-fails (which the
existing failure path then reports and keeps out of the lock).
2. syncLocaleFiles only diffed source against the lock, so keys the lock
considered translated but absent from a target locale file were never
re-sent. Target files now self-heal: per-locale missing keys are included
in that locale's batches regardless of the lock state.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
github-actions Bot
pushed a commit
that referenced
this pull request
Aug 10, 2026
## [1.4.3](v1.4.2...v1.4.3) (2026-08-10) ### Bug Fixes * validate batch translations and heal keys missing from target locales ([#9](#9)) ([36aa031](36aa031))
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
solid-translate translateagainst real OpenRouter providers reports success while translating nothing:Root causes, reproduced live:
generateObject+z.recordschema: aRecord<string,string>compiles to anadditionalProperties-only JSON schema. Gemini (via OpenRouter) silently returns{}for it — which validates — and OpenAI strict mode rejects the schema outright. Every batch "succeeded" empty, so no failure was recorded and the lock was committed for all 2887 keys: the exact poisoned state the lock is meant to prevent.syncLocaleFilesdiffs source against the lock only, so once poisoned (or after an interrupted run), keys missing from target files are never re-sent.checkflags them, buttranslatecan't fix them.Fix
translateBatchnow usesgenerateText+ tolerant JSON extraction (fences/prose stripped, envelope unwrapped) + strict validation: every requested key must come back as a non-empty string. One corrective retry for just the missing keys, then the batch throws — the existing failure path reports it and keeps failed keys out of the lock.syncLocaleFilescomputes per-locale missing keys (in source, absent from the target file) and includes them in that locale's batches regardless of lock state, with context hints from the committed lock entry. Deletion-only and no-changes early returns respect healing.Verified live against
google/gemini-2.5-flashvia OpenRouter: correct translations with placeholders preserved.Tests
17 new tests (
tests/translate-validation.test.ts): JSON extraction, envelope unwrap, missing/empty/non-string detection, the Gemini empty-object regression, invented-key filtering, per-locale healing, no-changes when complete, and failed-heal retry semantics. Full suite: 167 pass.🤖 Generated with Claude Code